package cinvestment;

import ilog.concert.IloLinearNumExpr;
import ilog.concert.IloNumVar;
import ilog.cplex.IloCplex;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import concurrent.Executer;
import support.AlgorithmSupport;
import util.Lang;
import util.MathUtil;
import util.NullOutputStream;

/**
 * This method concurrently solve M dual problems to save time
 * **/
public class CapacityInvestmentConcurrent {

	public CapacityInvestmentConcurrent(double delta, int productTypes,
			int simulationCount, double[] penalties, double c1) {
		this.delta = delta;
		this.N = productTypes;
		this.simulatedCount = simulationCount;
		this.p = Lang.clone(penalties);
		this.c1 = c1;
	}

	private List<Plant> plants;
	private double[][] demands;

	/**
	 * Number of kinds of products
	 */
	private int N;

	/**
	 * Penalty cost for unfilled demand
	 * **/
	// private double p = 1.0;
	// try to vary the penalty cost of different products
	private double[] p;

	/**
	 * Number of plants
	 */
	private int n;

	/**
	 * Tolerance for the gap between Upper bound and lower bound
	 * **/
	private double epsilon = 0.0005;

	/**
	 * Basic cost for flexible resource cost
	 * **/
	private double c1;
	private double delta;

	/**
	 * Sampling count of demand
	 * **/
	private int simulatedCount;

	/**
	 * iteration counter
	 * **/
	private int iter = 1;

	private void init() {
		this.demands = AlgorithmSupport.getDemands_uniform(simulatedCount, N, 0.0, 2.0);
		this.plants = AlgorithmSupport.createExtensivePlants(N);
		this.n = this.plants.size();
	}

	public List<Double[]> lshaped() {
		this.init();

		List<Double[]> finalResult = new ArrayList<>();

		List<Double> etList = new ArrayList<>();
		List<Double[]> EtList = new ArrayList<>();

		double[] Kv = new double[n];
		for (int j = 0; j < n; j++)
			Kv[j] = 1.0;

		double[] flexibleCosts = new double[n];
		for (int j = 0; j < n; j++)
			flexibleCosts[j] = this.getFlexibleCost(plants.get(j)
					.getAvailableProductsNumber());

		double[] Et = new double[n];

		BigDecimal et = BigDecimal.ZERO;
		BigDecimal Etk = BigDecimal.ZERO;

		iter = 1;
		while (true) {
			et = BigDecimal.ZERO;
			Etk = BigDecimal.ZERO;
			for (int j = 0; j < n; j++)
				Et[j] = 0.0;

			double masterObjV = Double.NEGATIVE_INFINITY;
			try {
				double theta_value = -100;
				System.out.println("---------- master problem -------- iter = "
						+ iter);
				if (iter > 1) {
					IloCplex master = new IloCplex();
					master.setOut(new NullOutputStream());
					IloLinearNumExpr obj = master.linearNumExpr();

					IloNumVar[] K = master
							.numVarArray(n, 0.0, Double.MAX_VALUE);
					IloNumVar theta = master.numVar(-100, Double.MAX_VALUE);

					obj.addTerm(1.0, theta);
					obj.addTerms(flexibleCosts, K);
					master.addMinimize(obj);

					for (int it = 0; it < etList.size(); it++) {
						master.addGe(master.sum(theta, master.scalProd(
								Lang.convertDouble2double(EtList.get(it)), K)),
								etList.get(it));
					}

					if (master.solve()) {
						Kv = master.getValues(K);
						theta_value = master.getValue(theta);
						masterObjV = master.getObjValue();

						System.out.print("decision variables for iter(" + iter
								+ ") = ");
					} else {
						System.out.println("unsolve status = "
								+ master.getStatus());
					}

					Lang.print(Kv);
					master.end();
				}
				// solve each dual problem
				Executer executer = new Executer(100, true);
				for (int m = 0; m < simulatedCount; m++) {
					executer.fork(new DualSolver(N, n, demands[m], Kv, this.p,
							plants, m));
				}
				List<DualResult> dualResults = executer.join();
				executer.shutdown();

				for (DualResult r : dualResults) {
					if (r != null) {
						et = et.add(BigDecimal.valueOf(r.getEtm()));
						Etk = Etk.add(BigDecimal.valueOf(r.getEtk()));
						Et = MathUtil.sum(Et, r.getEt());
					}
				}
				et = et.divide(BigDecimal.valueOf(simulatedCount), 3,
						BigDecimal.ROUND_HALF_EVEN);
				Etk = Etk.divide(BigDecimal.valueOf(simulatedCount), 3,
						BigDecimal.ROUND_HALF_EVEN);
				for (int j = 0; j < n; j++)
					Et[j] = BigDecimal
							.valueOf(-Et[j])
							.divide(BigDecimal.valueOf(simulatedCount), 3,
									BigDecimal.ROUND_HALF_EVEN).doubleValue();
				etList.add(et.doubleValue());
				EtList.add(Lang.convertdouble2Double(Et));

				double wt = et.subtract(Etk).doubleValue();
				double gap = wt - theta_value;
				double ratio = BigDecimal
						.valueOf(wt)
						.divide(BigDecimal.valueOf(theta_value), 7,
								BigDecimal.ROUND_HALF_EVEN).abs().doubleValue();

				System.out.println("gap = " + gap + "; ratio=" + ratio);
				if (theta_value >= wt || ratio > 0.9995) {
					Lang.print(Kv);

					Double[] fobj = { masterObjV };
					Double[] fcap = new Double[n];
					fcap = Lang.convertdouble2Double(Kv);
					finalResult.add(fobj);
					finalResult.add(fcap);

					break;
				}
				iter++;
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return finalResult;
	}

	/**
	 * Calculate the flexible cost for a plant based on the number of products
	 * it can produce. c1[1+(countj-1)*delta]
	 * 
	 * @param number
	 *            : the number of products the plant can produce
	 * **/
	private double getFlexibleCost(int number) {
		// linear cost in number
		return BigDecimal
				.valueOf(c1)
				.multiply(
						BigDecimal.valueOf(1).add(
								BigDecimal.valueOf(delta).multiply(
										BigDecimal.valueOf(number - 1))))
				.doubleValue();

		// // convex in number
		// return BigDecimal
		// .valueOf(c1)
		// .multiply(
		// BigDecimal.ONE.add(BigDecimal.valueOf(
		// Math.pow(number - 1, 1.5)).multiply(
		// BigDecimal.valueOf(delta)))).doubleValue();
	}
}
